7.1 Excepciones en Python
1️⃣ Qué es una excepción
Una excepción es un evento que ocurre durante la ejecución e interrumpe el flujo normal del programa porque algo ha ido mal (o porque queremos señalar una situación especial).
Ejemplos típicos:
- Dividir entre 0 →
ZeroDivisionError - Convertir
"hola"aint→ValueError - Acceder a una clave que no existe en un diccionario →
KeyError - Abrir un archivo que no existe →
FileNotFoundError
print(10 / 0) # ZeroDivisionError
2️⃣ Modelo formal de excepciones
En Python, las excepciones son objetos que representan condiciones anómalas detectadas en tiempo de ejecución.
El modelo sigue el patrón:
- Se detecta una condición excepcional.
- Se instancia un objeto excepción.
- Se lanza mediante raise.
- El intérprete busca un manejador compatible en la pila de llamadas.
- Si no lo encuentra → termina el programa.
Esto es un modelo basado en propagación por pila (stack unwinding).
3️⃣ Manejo básico de excepciones
🟩 try / except
La estructura más común:
try:
# código que puede fallar
...
except TipoDeError:
# qué hacer si falla con ese error
...
Ejemplo:
try:
n = int(input("Introduce un número: "))
print(10 / n)
except ValueError:
print("Eso no es un número válido.")
except ZeroDivisionError:
print("No se puede dividir entre 0.")
🟦 Capturar “cualquier excepción”
Se puede, pero conviene hacerlo con cuidado:
try:
...
except Exception as e:
print("Ha ocurrido un error:", e)
Captura el tipo más específico posible (ValueError, KeyError, etc.) y usa Exception solo si de verdad te encaja.
🟨 as e: guardar el error
Muy útil para mostrar o registrar el mensaje real:
try:
lista = [1, 2, 3]
print(lista[10])
except IndexError as e:
print("Índice fuera de rango:", e)
🟧 else: si NO hubo excepción
else se ejecuta solo si el try termina sin errores.
try:
n = int(input("Número: "))
except ValueError:
print("No es un número.")
else:
print("Perfecto, tu número es:", n)
Cuándo usarlo: para separar claramente “lo que puede fallar” (try) de “lo que debe pasar si todo va bien” (else).
🟥 finally: pase lo que pase
finally se ejecuta siempre, haya error o no. Se usa para limpieza: cerrar recursos, liberar cosas, etc.
f = None
try:
f = open("datos.txt", "r", encoding="utf-8")
contenido = f.read()
print(contenido)
except FileNotFoundError:
print("El archivo no existe.")
finally:
if f is not None:
f.close()
print("Archivo cerrado (si se llegó a abrir).")
4️⃣ Lanzar excepciones
raise
A veces el programa detecta un dato inválido y quiere detener la operación con un error controlado.
edad = -5
if edad < 0:
raise ValueError("La edad no puede ser negativa.")
🟩 Re-lanzar la excepción original
Útil cuando quieres hacer algo (log, mensaje) y luego dejar que el error siga su camino:
try:
n = int("hola")
except ValueError:
print("Registro: hubo un error de conversión")
raise # vuelve a lanzar el mismo error
🟦 Excepciones personalizadas
Cuando un error es “de tu dominio” (tu app, tu sistema), conviene crear una excepción propia.
class SaldoInsuficienteError(Exception):
pass
def retirar(saldo, cantidad):
if cantidad > saldo:
raise SaldoInsuficienteError("No hay saldo suficiente.")
return saldo - cantidad
try:
saldo = retirar(100, 200)
except SaldoInsuficienteError as e:
print("Operación rechazada:", e)
🟨 Con datos adicionales (mejorando la excepción)
class SaldoInsuficienteError(Exception):
def __init__(self, saldo, cantidad):
self.saldo = saldo
self.cantidad = cantidad
super().__init__(f"Saldo {saldo} insuficiente para retirar {cantidad}.")
raise SaldoInsuficienteError(100, 200)
5️⃣ Jerarquía de excepciones
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── ArithmeticError
├── LookupError
├── ValueError
├── TypeError
├── OSError
└── ...
Si capturas Exception, también capturas casi todo. Si capturas una excepción específica, no “tapas” errores inesperados.
⚠️ Evita capturar BaseException (incluye cosas como KeyboardInterrupt, SystemExit).
6️⃣ Excepciones frecuentes
ValueError: valor inválido (ej.int("hola"))TypeError: tipo incorrecto (ej."3" + 2)KeyError: clave inexistente en dictIndexError: índice fuera de rango en lista/tuplaFileNotFoundError: archivo no encontradoPermissionError: sin permisosAttributeError: atributo inexistenteImportError/ModuleNotFoundError: importacionesZeroDivisionError: división por cero
Ejemplo rápido de KeyError:
d = {"a": 1}
try:
print(d["b"])
except KeyError:
print("Esa clave no existe.")
7️⃣ Ejemplos completos típicos
🟩 Pedir número repetidamente hasta que sea correcto
while True:
try:
n = int(input("Número entero: "))
break
except ValueError:
print("Introduce un entero válido.")
print("OK:", n)
🟦 Función robusta con validación + raise
def calcular_media(lista):
if not lista:
raise ValueError("La lista no puede estar vacía.")
return sum(lista) / len(lista)
try:
print(calcular_media([]))
except ValueError as e:
print("Error:", e)
🟨 Varias excepciones en un solo except
try:
n = int(input("n: "))
print(10 / n)
except (ValueError, ZeroDivisionError) as e:
print("Entrada inválida o división por 0:", e)
✅ Buenas prácticas en el uso de excepciones
-
Utilizar excepciones únicamente para situaciones excepcionales Las excepciones deben emplearse para condiciones anómalas o estados inválidos, no como sustituto de estructuras de control normales (
if,for, etc.). -
Capturar la excepción más específica posible Se recomienda interceptar únicamente los tipos de error que realmente se pueden gestionar. Evitar capturas genéricas como
except Exceptionsalvo que exista una justificación clara (por ejemplo, en una capa superior de la aplicación). -
No silenciar errores deliberadamente Evitar construcciones como:
except:
passEste patrón oculta fallos reales y dificulta la depuración y el mantenimiento del sistema.
-
Proporcionar mensajes claros y significativos al lanzar excepciones Cuando se utiliza
raise, el mensaje asociado debe describir con precisión el problema y aportar contexto suficiente para facilitar la diagnosis. -
Aplicar el principio EAFP (Easier to Ask Forgiveness than Permission) En Python es idiomático intentar la operación y capturar la excepción si ocurre, en lugar de verificar previamente todas las condiciones posibles. Esto produce código más limpio y reduce verificaciones redundantes.
-
Permitir la propagación cuando no se puede gestionar el error localmente Si una función no puede resolver adecuadamente la excepción, debe dejarla propagarse hacia una capa superior en lugar de capturarla innecesariamente.
-
Preservar el traceback cuando se encapsulan excepciones Si se transforma una excepción técnica en una excepción de dominio, se recomienda utilizar:
raise NuevaExcepcion(...) from eEsto mantiene la traza original y facilita el análisis posterior.
-
Utilizar herramientas de logging en lugar de
printen entornos profesionales En aplicaciones reales, los errores deben registrarse mediante sistemas de logging que conserven el traceback completo. -
Consultar o mostrar el traceback cuando sea necesario para depuración Durante el desarrollo, puede utilizarse:
import traceback
traceback.print_exc()para obtener información detallada sobre la pila de llamadas.